﻿// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Microsoft.PowerFx.Core.App.ErrorContainers;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
using Microsoft.PowerFx.Core.IR.Nodes;
using Microsoft.PowerFx.Core.IR.Symbols;
using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
using Microsoft.PowerFx.Syntax;

#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable SA1649 // File name should match first type name

namespace Microsoft.PowerFx.Core.Texl.Builtins
{
    // Boolean(arg:s)
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanFunction : BuiltinFunction
    {
        public const string BooleanInvariantFunctionName = "Boolean";

        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        public BooleanFunction()
            : base(BooleanInvariantFunctionName, TexlStrings.AboutBoolean, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.String)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanArg1 };
        }
    }

    // Boolean(E:*[s])
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanFunction_T : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public BooleanFunction_T()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanT, FunctionCategories.Table, DType.EmptyTable, 0, 1, 1, DType.EmptyTable)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanTArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
            Contracts.Assert(returnType.IsTable);

            var arg = args[0];
            var argType = argTypes[0];
            fValid &= CheckStringColumnType(context, arg, argType, errors, ref nodeToCoercedTypeMap);

            var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
            returnType = rowType.ToTable();

            return fValid;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanT_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(arg:n)
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanNFunction : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        public BooleanNFunction()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanN, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.Number)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanNArg1 };
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanN_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(E:*[n])
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanNFunction_T : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public BooleanNFunction_T()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanNT, FunctionCategories.Table, DType.EmptyTable, 0, 1, 1, DType.EmptyTable)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanNTArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
            Contracts.Assert(returnType.IsTable);

            var arg = args[0];
            var argType = argTypes[0];
            fValid &= CheckNumericColumnType(context, arg, argType, errors, ref nodeToCoercedTypeMap);

            var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
            returnType = rowType.ToTable();

            return fValid;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanNT_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(arg:w)
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanWFunction : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        // Reusing BooleanN strings as they are generic for numbers
        public BooleanWFunction()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanW, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.Decimal)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            // Reusing BooleanN strings as they are generic for numbers
            yield return new[] { TexlStrings.BooleanWArg1 };
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanW_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(E:*[n])
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanWFunction_T : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        // Reusing BooleanN strings as they are generic for numbers
        public BooleanWFunction_T()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanWT, FunctionCategories.Table, DType.EmptyTable, 0, 1, 1, DType.EmptyTable)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            // Reusing BooleanN strings as they are generic for numbers
            yield return new[] { TexlStrings.BooleanWTArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
            Contracts.Assert(returnType.IsTable);

            var arg = args[0];
            var argType = argTypes[0];
            fValid &= CheckColumnType(context, arg, argType, DType.Decimal, errors, ref nodeToCoercedTypeMap);

            var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
            returnType = rowType.ToTable();

            return fValid;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            // Reusing BooleanN strings as they are generic for numbers
            return StringResources.TryGet("AboutBooleanWT_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(arg:b)
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanBFunction : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        public BooleanBFunction()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanB, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.Boolean)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanBArg1 };
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanB_" + paramName, out paramDescription, locale);
        }

        /// <summary>
        /// If arg's result type is Boolean, no need to make a function call to Boolean() function. It can just emit arg directly.
        /// </summary>
        internal override IR.Nodes.IntermediateNode CreateIRCallNode(PowerFx.Syntax.CallNode node, IRTranslator.IRTranslatorContext context, List<IntermediateNode> args, ScopeSymbol scope)
        {
            if (args[0].IRContext.ResultType._type == DType.Boolean)
            {
                return args[0];
            }
            else
            {
                return base.CreateIRCallNode(node, context, args, scope);
            }
        }
    }

    // Boolean(E:*[b])
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanBFunction_T : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public BooleanBFunction_T()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanBT, FunctionCategories.Table, DType.EmptyTable, 0, 1, 1, DType.EmptyTable)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanBTArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
            Contracts.Assert(returnType.IsTable);

            var arg = args[0];
            var argType = argTypes[0];
            fValid &= CheckBooleanColumnType(context, arg, argType, errors, ref nodeToCoercedTypeMap);

            var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
            returnType = rowType.ToTable();

            return fValid;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanBT_" + paramName, out paramDescription, locale);
        }

        /// <summary>
        /// If arg is Table of boolean, no need to make a function call to boolean function. It can just emit the table 
        /// arg directly.
        /// </summary>
        internal override IR.Nodes.IntermediateNode CreateIRCallNode(PowerFx.Syntax.CallNode node, IRTranslator.IRTranslatorContext context, List<IntermediateNode> args, ScopeSymbol scope)
        {
            var child = args[0];
            var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
            var booleanTReturnType = rowType.ToTable();
            if (child.IRContext.ResultType._type == booleanTReturnType)
            {
                return child;
            }
            else
            {
                return base.CreateIRCallNode(node, context, args, scope);
            }
        }
    }

    // Boolean(arg:L)
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanLFunction : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        public BooleanLFunction()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanL, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.OptionSetValue)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanLArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var argType = argTypes[0];

            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap) &&
                         argType.IsOptionSetBackedByBoolean;

            returnType = DType.Boolean;

            return fValid;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanL_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(E:*[L])
    // Corresponding Excel and DAX function: Boolean
    internal sealed class BooleanLFunction_T : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public BooleanLFunction_T()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBooleanLT, FunctionCategories.Table, DType.EmptyTable, 0, 1, 1, DType.EmptyTable)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanLTArg1 };
        }

        public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
        {
            var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
            Contracts.Assert(returnType.IsTable);

            var arg = args[0];
            var argType = argTypes[0];

            // Check first that it is a single column table of option set values.
            // Next check that the column is not only option sets, but also backed by Booleans.
            if (CheckColumnType(context, arg, argType, DType.OptionSetValue, errors, ref nodeToCoercedTypeMap) &&
                argType.TypeTree.GetPairs().First().Value.IsOptionSetBackedByBoolean)
            {
                var rowType = DType.EmptyRecord.Add(new TypedName(DType.Boolean, ColumnName_Value));
                returnType = rowType.ToTable();

                return true;
            }

            return false;
        }

        public override bool TryGetParamDescription(string paramName, out string paramDescription, string locale = null)
        {
            Contracts.AssertNonEmpty(paramName);

            return StringResources.TryGet("AboutBooleanLT_" + paramName, out paramDescription, locale);
        }
    }

    // Boolean(arg:O)
    internal sealed class BooleanFunction_UO : BuiltinFunction
    {
        public override bool IsSelfContained => true;

        public override bool SupportsParamCoercion => false;

        public override bool IsAllowedInSimpleExpressions => true;

        public BooleanFunction_UO()
            : base(BooleanFunction.BooleanInvariantFunctionName, TexlStrings.AboutBoolean, FunctionCategories.Text, DType.Boolean, 0, 1, 1, DType.UntypedObject)
        {
        }

        public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
        {
            yield return new[] { TexlStrings.BooleanArg1 };
        }
    }
}

#pragma warning restore SA1402 // File may only contain a single type
#pragma warning restore SA1649 // File name should match first type name
